/*
 * File.cpp
 *
 *  Created on: Mar 25, 2010
 *      Author: Lukasz Spintzyk
 */

#include "ph.h"
#include "File.h"
#include "InodeReader.h"
#include "BlockReader.h"
#include "SuperBlock.h"
#include "BlocksNumCache.h"

File::File(const std::string& path,
		InodePtr fileInode,
		InodeManager& inodeMgr,
		BlockManager& blockMgr)
	:d_fileInode(fileInode)
	,name(path)
	,d_inodeMgr(inodeMgr)
	,d_blockMgr(blockMgr)
	,d_blocksCache(new BlocksNumCache(d_fileInode, d_inodeMgr, d_blockMgr))
	,d_buffer(new byte[d_inodeMgr.getSuperBlock().d_blockSize])

{
}

/*****************************************************************************/
/*****************************************************************************/


File::File(const File& file)
	:d_fileInode(file.d_fileInode)
	,name(file.name)
	,d_inodeMgr(file.d_inodeMgr)
	,d_blockMgr(file.d_blockMgr)
	,d_blocksCache(new BlocksNumCache(*file.d_blocksCache))
	,d_buffer(new byte[d_inodeMgr.getSuperBlock().d_blockSize])
{
	memcpy(d_buffer, file.d_buffer, d_inodeMgr.getSuperBlock().d_blockSize);
}


/*****************************************************************************/
/*****************************************************************************/

File::~File()
{
	//TODO free reserved memory by new
	delete d_blocksCache;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::read(char *buf, size_t size, off_t offset)
{
	size = normalizeSizePossibleToRead(size, offset);
	uint32_t read_size = size;
	uint32_t bytes_read = 0;

	bytes_read += copyFirstBlock(buf, size, offset);

	assert(size >= bytes_read);
	bytes_read += copyInternalBlocks(buf+bytes_read, size-bytes_read, offset+bytes_read);

	assert(size >= bytes_read);
	bytes_read += copyLastBlock(buf+bytes_read, size - bytes_read, offset + bytes_read);

	assert(read_size == bytes_read);
	return bytes_read;
}

uint32_t File::copyFirstBlock(char* buf, size_t size,off_t offset)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	uint32_t block_index = offset / block_size;
	uint32_t block_offset = offset % block_size;

	uint32_t bytes_num_to_copy
		= std::min(size,static_cast<size_t>(block_size - block_offset));

	return copyRestOfBlock(buf, block_index, block_offset, bytes_num_to_copy);
}

uint32_t File::copyInternalBlocks(char* buf, size_t size,off_t offset)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	uint32_t blocks_num_to_copy = size / block_size;
	uint32_t block_index = offset / block_size;
	return copyFullBlocks(buf, block_index, blocks_num_to_copy);
}


uint32_t File::copyLastBlock(char* buf, size_t size,off_t offset)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	uint32_t block_index = offset / block_size;
	assert(size <= block_size);
	return copyRestOfBlock(buf, block_index, 0, size);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::write(const char *buf, size_t size, off_t offset)
{
	allocateBlocksIfNeeded(size, offset);
	uint32_t read_size = size;
	uint32_t bytes_written = 0;

	bytes_written += writeToFirstBlock(buf, size, offset);

	assert(size >= bytes_written);
	bytes_written += writeToInternalBlocks(buf+bytes_written, size-bytes_written, offset+bytes_written);

	assert(size >= bytes_written);
	bytes_written += writeToLastBlock(buf+bytes_written, size - bytes_written, offset + bytes_written);

	assert(read_size == bytes_written);


	return bytes_written;

}

uint32_t File::allocateBlocksIfNeeded(size_t size, off_t offset)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	if (offset + size > block_size * d_fileInode->d_blocks)
	{
		uint32_t additional_blocks =(offset + size) - block_size * d_fileInode->d_blocks;
		additional_blocks = additional_blocks/block_size + 100;

		d_inodeMgr.reserveBlocksForInode(d_fileInode, additional_blocks);
		// TODO handle reserve fail.
		d_fileInode->d_size = offset+size;
		d_inodeMgr.setInode(d_fileInode->d_num, d_fileInode);
		return additional_blocks;
	}

	// TODO refactor
	if (d_fileInode->d_size < offset + size)
	{
		d_fileInode->d_size = offset + size;
		d_inodeMgr.setInode(d_fileInode->d_num, d_fileInode);
	}

	return 0 ;
}

uint32_t File::writeToFirstBlock(const char* buf, size_t size,off_t offset)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	uint32_t block_index = offset / block_size;
	uint32_t block_offset = offset % block_size;

	uint32_t bytes_num_to_copy
		= std::min(size,static_cast<size_t>(block_size - block_offset));

	return writeRestOfBlock(buf, block_index, block_offset, bytes_num_to_copy);
}

uint32_t File::writeToInternalBlocks(const char* buf, size_t size,off_t offset)
{
	if (size)
	{
		uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
		uint32_t blocks_num_to_write = size / block_size;
		uint32_t block_index = offset / block_size;
		return writeFullBlocks(buf, block_index, blocks_num_to_write);
	}
	return size;
}

uint32_t File::writeToLastBlock(const char* buf, size_t size,off_t offset)
{
	if (size)
	{
		uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
		uint32_t block_index = offset / block_size;
		assert(size <= block_size);
		return writeRestOfBlock(buf, block_index, 0, size);
	}
	return size;
}


/*****************************************************************************/
/*****************************************************************************/

size_t File::normalizeSizePossibleToRead(size_t size, off_t offset)
{
	uint32_t file_size = d_fileInode->d_size;
	if (offset > file_size)
	{
		return 0;
	}
	file_size -= offset;
	return std::min(size, static_cast<size_t>(file_size) );
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::copyRestOfBlock(char* dest_buf, uint32_t src_block_index, uint32_t block_offset, uint32_t bytes_num_to_copy)
{
	if (bytes_num_to_copy)
	{
		uint32_t block_num = d_blocksCache->cachedBlkIndex2Num(src_block_index);
		d_blockMgr.getBlock(block_num, d_buffer);
		memcpy(dest_buf, d_buffer+block_offset, bytes_num_to_copy);
	}
	return bytes_num_to_copy;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::copyFullBlocks(char* dest_buf, uint32_t src_block_index, uint32_t blocks_num_to_copy)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	if (blocks_num_to_copy)
	{
		for (uint32_t i = 0; i < blocks_num_to_copy; ++i)
		{
			uint32_t block_num = d_blocksCache->cachedBlkIndex2Num(i+src_block_index);
			d_blockMgr.getBlock(block_num, dest_buf + i*block_size);
		}
	}
	return block_size*blocks_num_to_copy;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::writeRestOfBlock(const char* dest_buf, uint32_t src_block_index, uint32_t block_offset, uint32_t bytes_num_to_copy)
{
	if (bytes_num_to_copy)
	{
		uint32_t block_num = d_blocksCache->cachedBlkIndex2Num(src_block_index);
		assert(block_num != 0);
		d_blockMgr.getBlock(block_num, d_buffer);
		memcpy(d_buffer+block_offset, dest_buf, bytes_num_to_copy);
		d_blockMgr.setBlock(block_num, d_buffer);
	}
	return bytes_num_to_copy;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t File::writeFullBlocks(const char* dest_buf, uint32_t src_block_index, uint32_t blocks_num_to_copy)
{
	uint32_t block_size = d_inodeMgr.getSuperBlock().d_blockSize;
	if (blocks_num_to_copy)
	{
		for (uint32_t i = 0; i < blocks_num_to_copy; ++i)
		{
			uint32_t block_num = d_blocksCache->cachedBlkIndex2Num(i+src_block_index);
			d_blockMgr.setBlock(block_num, dest_buf + i*block_size);
		}
	}
	return block_size*blocks_num_to_copy;
}

